/*
Copyright 2002-2009 Corey Trager
Distributed under the terms of the GNU General Public License
*/

using System;
using System.Web;
using System.Collections.Specialized;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Data;
using System.Text;

namespace btnet
{

	public class VersionControl
	{

		///////////////////////////////////////////////////////////////////////
		public static string run_and_capture(string filename, string args, string working_dir)
		{
			System.Diagnostics.Process p = new System.Diagnostics.Process();

			p.StartInfo.WorkingDirectory = working_dir;
            p.StartInfo.Arguments = args;
			p.StartInfo.FileName = filename;

			p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
			p.StartInfo.UseShellExecute = false;
			p.StartInfo.RedirectStandardOutput = true;
			p.StartInfo.RedirectStandardError = true;

			Bug_Util.write_to_log(filename + " " + args);

			p.Start();
			string stdout = p.StandardOutput.ReadToEnd();
			p.WaitForExit();
			stdout += p.StandardOutput.ReadToEnd();

			string error = p.StandardError.ReadToEnd();

			if (error != "")
			{
                Bug_Util.write_to_log("stderr:" + error);
                Bug_Util.write_to_log("stdout:" + stdout);
			}

            if (error != "" 
            && !error.Contains("RUNTIME_PREFIX")) // ignore the git "RUNTIME_PREFIX" error
			{
				string msg = "<div style='color:red; font-weight: bold; font-size: 10pt;'>";
				msg += "<br>Error executing git or hg command:";
				msg += "<br>Error: " + error;
				msg += "<br>Command: " + filename + " " + args;
                HttpContext.Current.Response.Write(msg);
                HttpContext.Current.Response.End();
                return msg;
			}
			else
			{
                Bug_Util.write_to_log("stdout:" + stdout);
				return stdout;
			}
		}


		///////////////////////////////////////////////////////////////////////
		public static string run_git(string args, string working_dir)
		{
            string filename = Bug_Util.get_setting("GitPathToGit", "[path to git.exe?]");
			return run_and_capture(filename, args, working_dir);
		}

		///////////////////////////////////////////////////////////////////////
		public static string run_hg(string args, string working_dir)
		{
            string filename = Bug_Util.get_setting("MercurialPathToHg", "[path to hg.exe?]");
			return run_and_capture(filename, args, working_dir);
		}


		///////////////////////////////////////////////////////////////////////
		public static string run_svn(string args_without_password, string repo)
		{
			// run "svn.exe" and capture its output

			System.Diagnostics.Process p = new System.Diagnostics.Process();
            string filename = Bug_Util.get_setting("SubversionPathToSvn", "svn");
            p.StartInfo.FileName = filename;
			p.StartInfo.UseShellExecute = false;
			p.StartInfo.RedirectStandardOutput = true;
			p.StartInfo.RedirectStandardError = true;

			args_without_password += " --non-interactive";

			string more_args = Bug_Util.get_setting("SubversionAdditionalArgs", "");			
			if (more_args != "")
			{
				args_without_password += " " + more_args;
			}

			string args_with_password = args_without_password;

			// add a username and password to the args
            string username_and_password_and_websvn = Bug_Util.get_setting(repo, "");

            string[] parts = Bug_Util.rePipes.Split(username_and_password_and_websvn);
			if (parts.Length > 1)
			{
				if (parts[0] != "" && parts[1] != "")
				{
					args_with_password += " --username ";
					args_with_password += parts[0];
					args_with_password += " --password ";
					args_with_password += parts[1];
				}
			}
			
			p.StartInfo.Arguments = args_with_password;
			p.Start();
			string stdout = p.StandardOutput.ReadToEnd();
			p.WaitForExit();
			stdout += p.StandardOutput.ReadToEnd();

			string error = p.StandardError.ReadToEnd();

			if (error != "")
			{
                Bug_Util.write_to_log("stderr:" + error);
                Bug_Util.write_to_log("stdout:" + stdout);
			}

			if (error != "")
            {
                string msg = "<div style='color:red; font-weight: bold; font-size: 10pt;'>";
                msg += "<br>Error executing svn command:";
                msg += "<br>Error: " + error;
                msg += "<br>Command: " + filename + " " + args_without_password;
                if (error.Contains("File not found"))
                {
                    msg += "<br><br>***** Has this file been deleted or renamed? See the following links:";
                    msg += "<br><a href=http://svn.collab.net/repos/svn/trunk/doc/user/svn-best-practices.html>http://svn.collab.net/repos/svn/trunk/doc/user/svn-best-practices.html</a>";
                    msg += "<br><a href=http://subversion.open.collab.net/articles/best-practices.html>http://subversion.open.collab.net/articles/best-practices.html</a>";
                    msg += "</div>";
                }
                HttpContext.Current.Response.Write(msg);
                HttpContext.Current.Response.End();
                return msg;
            }
			else
            {
                Bug_Util.write_to_log("stdout:" + stdout);
                return stdout;
            }
		}
		

		///////////////////////////////////////////////////////////////////////
		public static string visual_diff(string unified_diff_text, string left_in, string right_in, ref string left_out, ref string right_out)
		{
			Regex regex = new Regex("\n");
			string line = "";

			string diff_text = unified_diff_text;

			// get rid of lines we don't need
			int pos = unified_diff_text.IndexOf("\n@@");
			if (pos > -1)
			{
				diff_text = unified_diff_text.Substring(pos+1);
			}

			if (diff_text == "")
			{
				return "No differences.";
			}

			// first, split everything into lines
			string[] diff_lines = regex.Split(diff_text.Replace("\r\n","\n"));

			// html encode
			string left_text = HttpUtility.HtmlEncode(left_in);
			string right_text = HttpUtility.HtmlEncode(right_in);
			// split into lines
			string[] left_lines = regex.Split(left_text.Replace("\r\n","\n"));
			string[] right_lines = regex.Split(right_text.Replace("\r\n","\n"));


			// for formatting line numbers
			int max_lines = left_lines.Length;
			if (right_lines.Length > left_lines.Length) max_lines = right_lines.Length;

			// I just want to pad left a certain number of places
			// probably any 5th grader would know how to do this better than me
			string blank = "";
			int digit_places = 1;
			if (max_lines > 9)	digit_places++;
			if (max_lines > 99) digit_places++;
			if (max_lines > 999) digit_places++;
			if (max_lines > 9999) digit_places++;
			if (max_lines > 99999) digit_places++;
			if (max_lines > 999999) digit_places++;


			int lx = 0;
			int rx = 0;
			int dx = 0;

			StringBuilder sL = new StringBuilder();
			StringBuilder sR = new StringBuilder();

			bool minus_followed_by_plus = false;

			// L E F T
			// L E F T
			// L E F T

			//sL.Append("<div class=difffile>" + "left"  + "</div>");
			//sR.Append("<div class=difffile>" + "right" + "</div>");

			while (dx < diff_lines.Length)
			{
				line = diff_lines[dx];
				if (line.StartsWith("@@ -") && line.Contains(" @@"))
				{

					// See comment at the top of this file explaining Unified Diff Format
					// Parse out the left start line.  For example, the "38" here:
					// @@ -38,18 +39,12 @@
					// Note that the range could also be specified as follows, with the number of lines assumed to be 1
					// @@ -1 +1,2 @@


					int pos1 = line.IndexOf("-");
					int comma_pos = line.IndexOf(",", pos1);
					if (comma_pos == -1)
					{
						comma_pos = 9999;
					}
					int pos2 = Math.Min(line.IndexOf(" ", pos1), comma_pos);
					string left_start_line_string = line.Substring(pos1 + 1, pos2 - (pos1 + 1));
					int start_line = Convert.ToInt32(left_start_line_string);
					start_line -= 1; // adjust for zero based index


					// advance through left file until we hit the starting line of the range
					while (lx < start_line)
					{
						sL.Append("<span class=diffnum>");
						sL.Append(Convert.ToString(lx+1).PadLeft(digit_places,'0'));
						sL.Append(" </span>");
						sL.Append(left_lines[lx++]);
						sL.Append("\n");
					}


					// we are positioned in the left file at the start of the diff blockk
					dx++;
					line = diff_lines[dx];
					minus_followed_by_plus = false;
					while (dx < diff_lines.Length
					&& !(line.StartsWith("@@ -") && line.EndsWith(" @@")))
					{
						if (line.StartsWith("+"))
						{
							if (!minus_followed_by_plus)
							{
								sL.Append("<span class=diffnum>");
								sL.Append(blank.PadLeft(digit_places,' '));
								sL.Append(" </span>");
								sL.Append("<span class=diffblank>&nbsp;&nbsp;&nbsp;&nbsp;</span>\n");
							}
							minus_followed_by_plus = false;
						}
						else if (line.StartsWith("-"))
						{

							sL.Append("<span class=diffnum>");
							sL.Append(Convert.ToString(lx+1).PadLeft(digit_places,'0'));
							sL.Append(" </span>");

							minus_followed_by_plus = false;
							if (dx < diff_lines.Length-2)
							{
								// a minus/plus combo is a change
								if (diff_lines[dx+1].StartsWith("+"))
								{
									minus_followed_by_plus = true;
								}
							}

							if (minus_followed_by_plus)
							{
								sL.Append("<span class=diffchg>");
							}
							else
							{
								sL.Append("<span class=diffdel>");
							}
							sL.Append(left_lines[lx++]);
							sL.Append("</span>\n");
						}
						else if (line.StartsWith("\\") || line == "")
						{
							minus_followed_by_plus = false;
							//break;
						}
						else
						{
							sL.Append("<span class=diffnum>");
							sL.Append(Convert.ToString(lx+1).PadLeft(digit_places,'0'));
							sL.Append(" </span>");
							sL.Append(left_lines[lx++]);
							sL.Append("\n");
							minus_followed_by_plus = false;
						}

						dx++;

						if (dx < diff_lines.Length)
						{
							line = diff_lines[dx];
						}
					} // end of range block
				}

				if (dx < diff_lines.Length && line.StartsWith("@@ -") && line.Contains(" @@"))
				{
					continue;
				}
				else
				{
					break;
				}

			} // end of all blocks

			// advance through left file until we hit the starting line of the range

			while (lx < left_lines.Length)
			{
				sL.Append("<span class=diffnum>");
				sL.Append(Convert.ToString(lx+1).PadLeft(digit_places,'0'));
				sL.Append(" </span>");
				sL.Append(left_lines[lx++]);
				sL.Append("\n");
			}



			// R I G H T
			// R I G H T
			// R I G H T
			dx = 0;
			while (dx < diff_lines.Length)
			{
				line = diff_lines[dx];
				if (line.StartsWith("@@ -") && line.Contains(" @@"))
				{

					// See comment at the top of this file explaining Unified Diff Format

					// parse out the right start line.  For example, the "39" here: @@ -38,18 +39,12 @@
					int pos1 = line.IndexOf("+");

					int pos2 = line.IndexOf(",", pos1);
					if (pos2 == -1) pos2 = line.IndexOf(" ", pos1);

					string right_start_line_string = line.Substring(pos1 + 1, pos2 - (pos1 + 1));
					int start_line = Convert.ToInt32(right_start_line_string);
					start_line -= 1; // adjust for zero based index

					// advance through right file until we hit the starting line of the range
					while (rx < start_line)
					{
						sR.Append("<span class=diffnum>");
						sR.Append(Convert.ToString(rx+1).PadLeft(digit_places,'0'));
						sR.Append(" </span>");
						sR.Append(right_lines[rx++]);
						sR.Append("\n");
					}


					// we are positioned in the right file at the start of the diff block
					dx++;
					line = diff_lines[dx];
					minus_followed_by_plus = false;
					while (dx < diff_lines.Length && !(line.StartsWith("@@ -") && line.Contains(" @@")))
					{
						if (line.StartsWith("-"))
						{
							minus_followed_by_plus = false;
							if (dx < diff_lines.Length-2)
							{
								// a minus/plus combo is a change
								if (diff_lines[dx+1].StartsWith("+"))
								{
									minus_followed_by_plus = true;
								}
							}

							// if THIS is left side of a change
							if (!minus_followed_by_plus)
							{
								sR.Append("<span class=diffnum>");
								sR.Append(blank.PadLeft(digit_places,' '));
								sR.Append(" </span>");
								sR.Append("<span class=diffblank>&nbsp;&nbsp;&nbsp;&nbsp;</span>\n");
							}


						}
						else if (line.StartsWith("+"))
						{
							sR.Append("<span class=diffnum>");
							sR.Append(Convert.ToString(rx+1).PadLeft(digit_places,'0'));
							sR.Append(" </span>");
							if (minus_followed_by_plus)
							{
								sR.Append("<span class=diffchg>");
							}
							else
							{
								sR.Append("<span class=diffadd>");
							}
							sR.Append(right_lines[rx++]);
							sR.Append("</span>\n");
							minus_followed_by_plus = false;
						}
						else if (line.StartsWith("\\") || line == "")
						{
							minus_followed_by_plus = false;
							//break;
						}
						else
						{
							sR.Append("<span class=diffnum>");
							sR.Append(Convert.ToString(rx+1).PadLeft(digit_places,'0'));
							sR.Append(" </span>");
							sR.Append(right_lines[rx++]);
							sR.Append("\n");
							minus_followed_by_plus = false;
						}

						dx++;

						if (dx < diff_lines.Length)
						{
							line = diff_lines[dx];
						}

					} // end of range block
				}

				if (dx < diff_lines.Length && line.StartsWith("@@ -") && line.EndsWith(" @@"))
				{
					continue;
				}
				else
				{
					break;
				}

			} // end of all blocks

			// advance through right file until we're done

			while (rx < right_lines.Length)
			{
				sR.Append("<span class=diffnum>");
				sR.Append(Convert.ToString(rx+1).PadLeft(digit_places,'0'));
				sR.Append(" </span>");
				sR.Append(right_lines[rx++]);
				sR.Append("\n");
			}

			left_out = sL.ToString();
			right_out = sR.ToString();

			return "";
		}

/*

hg

*/

		
		///////////////////////////////////////////////////////////////////////
		public static string hg_log(string repo, string revision, string file_path)
		{
		    
		    string args = "log  --style btnet -r :";
		    args += revision;
		    args += " \"";
		    args += file_path;
		    args += "\"";
		        
			string result = VersionControl.run_hg(args, repo);
			return result;
		}
		
		///////////////////////////////////////////////////////////////////////
		public static string hg_blame(string repo, string file_path, string revision)
		{
			string args = "blame -u -d -c -l -v -r ";
			args += revision;
			args += " \"";
			args += file_path;
			args += "\"";

			string result = VersionControl.run_hg(args, repo);
			return result;
		}
		
		///////////////////////////////////////////////////////////////////////
		public static string hg_get_file_contents(string repo, string revision, string file_path)
		{
		
		    string args = "cat -r ";
		    args += revision;
		    args += " \"";
		    args += file_path;
		    args += "\"";
		
		    string result = VersionControl.run_hg(args, repo);
		    return result;
		}
		
		///////////////////////////////////////////////////////////////////////
		public static string hg_get_unified_diff_two_revisions(string repo, string revision0, string revision1, string file_path)
		{

			string	args = "diff -r ";
			args += revision0;
			args += " -r ";
			args += revision1;

			args += " \"";
			args += file_path;
			args += "\"";

			string result = VersionControl.run_hg(args, repo);
			return result;
		}
		
/*

git

*/
		
		///////////////////////////////////////////////////////////////////////
		public static string git_log(string repo, string commit, string file_path)
		{

			string args = "log --name-status --date=iso ";
			args += commit;
			args += " -- \"";
			args += file_path;
			args += "\"";

			string result = VersionControl.run_git(args, repo);
			return result;
		}		
		///////////////////////////////////////////////////////////////////////
		public static string git_blame(string repo, string file_path, string commit)
		{
		    
		    string args = "blame ";
		    args += " -- \"";
		    args += file_path;
		    args += "\" ";
		    args += commit;
		        
			string result = VersionControl.run_git(args, repo);
			return result;
		}

		///////////////////////////////////////////////////////////////////////
		public static string git_get_file_contents(string repo, string commit, string file_path)
		{

			string args = "show --pretty=raw ";
			args += commit;
			args += ":\"";
			args += file_path;
			args += "\"";

			string result = VersionControl.run_git(args, repo);
			return result;
		}

		
		///////////////////////////////////////////////////////////////////////
		public static string git_get_unified_diff_two_commits(string repo, string commit0, string commit1, string file_path)
		{

			string	args = "diff ";
			args += commit0;
			args += " ";
			args += commit1;

			args += " -- \"";
			args += file_path;
			args += "\"";

			string result = VersionControl.run_git(args, repo);
			return result;
		}


		///////////////////////////////////////////////////////////////////////
		public static string git_get_unified_diff_one_commit(string repo, string commit, string file_path)
		{

			string args = "show  --pretty=format: ";
			args += commit;

			args += " -- \"";
			args += file_path;
			args += "\"";

			string result = VersionControl.run_git(args, repo);
			return result;
		}

/*

svn

*/

		///////////////////////////////////////////////////////////////////////
		public static string svn_log(string repo, string file_path, int rev)
		{
			StringBuilder args = new StringBuilder();

			args.Append("log ");
			args.Append(repo);
			args.Append(file_path.Replace(" ", "%20"));
			args.Append("@" + Convert.ToString(rev)); // peg to revision rev in case file deleted
			args.Append(" -r ");
			args.Append(Convert.ToString(rev)); // view log from beginning to rev
			args.Append(":0 --xml -v");

			return run_svn(args.ToString(), repo);
		}


		///////////////////////////////////////////////////////////////////////
		public static string svn_blame(string repo, string file_path, int rev)
		{
			StringBuilder args = new StringBuilder();

			args.Append("blame ");
			args.Append(repo);
			args.Append(file_path.Replace(" ", "%20"));
			args.Append("@");
			args.Append(Convert.ToString(rev));
			args.Append(" --xml");

			return run_svn(args.ToString(), repo);
		}


		///////////////////////////////////////////////////////////////////////
		public static string svn_cat(string repo, string file_path, int rev)
		{
			StringBuilder args = new StringBuilder();

			args.Append("cat ");
			args.Append(repo);
			args.Append(file_path.Replace(" ", "%20"));
			args.Append("@");
			args.Append(Convert.ToInt32(rev));

			return run_svn(args.ToString(), repo);
		}

		///////////////////////////////////////////////////////////////////////
		public static string svn_diff(string repo, string file_path, int revision, int old_revision)
		{
			StringBuilder args = new StringBuilder();

			if (old_revision != 0)
			{
				args.Append("diff -r ");

				args.Append(Convert.ToString(old_revision));
				args.Append(":");
				args.Append(Convert.ToString(revision));
				args.Append(" ");
				args.Append(repo);
				args.Append(file_path.Replace(" ", "%20"));
			}
			else
			{
				args.Append("diff -c ");
				args.Append(Convert.ToString(revision));
				args.Append(" ");
				args.Append(repo);
				args.Append(file_path.Replace(" ","%20"));
			}

			string result = run_svn(args.ToString(), repo);
			return result;
		}

    } 
}
